/*
    3D Office Environment
    Copyright (C) 2011  Till Merker

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "BaseOfficeScene.h"
#include <OgrePanelOverlayElement.h>
#include <OgreTextAreaOverlayElement.h>

namespace OfficeEnv {
    
BaseOfficeScene::~BaseOfficeScene() {
    if(mRoot) mRoot->destroySceneManager(mSceneMgr);
}

void BaseOfficeScene::init(Root *root, int width, int height, int numDesktops, String sceneFile) {
    mRoot = root;
    mWidth = width;
    mHeight = height;
    mNumDesktops = numDesktops;
    mUp = getUpVector();
    
    /* Create scene manager */
    mSceneMgr = createSceneManager();
    
    /* Create desktop mesh. Width is fixed, height calculated from aspect ratio of the screen */
    mDeskEntWidth = 96;
    mDeskEntHeight = 96 * height / width;
    Plane plane(Vector3::NEGATIVE_UNIT_Z, 0);
    MeshManager::getSingleton().createPlane("DeskMesh", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, plane,
	mDeskEntWidth, mDeskEntHeight, 1, 1, true, 1, 1, 1, Vector3::NEGATIVE_UNIT_Y);
	   
    /* Load world */
    loadScene(sceneFile);
    
    /* Get camera orientation and position */
    mCameraPos = mCamera->getPosition();
    mCameraOrient = mCamera->getOrientation();
    
    /* Calculate mYaw and mPitch from camera orientation */
    Plane p(mUp, 0);
    Vector3 proj = p.projectVector(mCamera->getDirection());	
    Vector3 target = Vector3::UNIT_X;	
    Radian signed_angle (atan2(  mUp.dotProduct( proj.crossProduct(target) ), proj.dotProduct(target) )); 		
    mPitch = mCamera->getRealOrientation().getPitch();
    mYaw = - signed_angle - Degree(90);
    
    /* Create desktop and frame entities */
    for(int i = 0; i < mNumDesktops; i++) {
 	getDesktopEntity(i, &mDesktopNode[i], &mDesktopEnt[i]);

	char frameEntName[16];
	snprintf((char*)&frameEntName, 16, "FrameEnt%i", i); 
	
	SceneNode *frameNode = mDesktopNode[i]->createChildSceneNode();
	mFrameEnt[i] = mSceneMgr->createEntity(frameEntName, "Frame.mesh");		    
	frameNode->attachObject(mFrameEnt[i]);    
	/* The frame mesh's size is 80:60, scale according to desktop mesh size */
	frameNode->setScale(mDeskEntWidth / 80.0f, mDeskEntHeight / 60.0f, 1);	
    }
    
    /* Create crosshair */
    OverlayManager &om = OverlayManager::getSingleton();
    Overlay *ovl = om.create("CrosshairOvl");
    Ogre::OverlayContainer *ovrcntScreenpanel = static_cast<Ogre::OverlayContainer*>(Ogre::OverlayManager::getSingleton().createOverlayElement("Panel","CrosshairPanel"));
    float crosshairWidth = 0.03f;
    float crosshairHeight = crosshairWidth * width / height;
    ovrcntScreenpanel->setPosition(0.5 - crosshairWidth / 2, 0.5 - crosshairHeight / 2);
    ovrcntScreenpanel->setDimensions(crosshairWidth, crosshairHeight);
    ovrcntScreenpanel->setMaterialName("CrosshairMat");
    ovrcntScreenpanel->show();
    ovl->add2D(ovrcntScreenpanel);
    ovl->show();       
}

void BaseOfficeScene::startAnimation(CameraAnimation animation, int param) {
    float distance;    
        
    mCurrentAnimation = animation;
    switch(mCurrentAnimation) {
	case CA_Switch:
	    /* Switch to desktop #param */
	    mAnimFrames = 30;
	    
	    mCameraPos = mCamera->getPosition();
	    mCameraOrient = mCamera->getOrientation();	    

	    mAnimOrientStart = mCamera->getOrientation();
	    mAnimOrientEnd = mDesktopNode[param]->getOrientation();	    
	    
	    mAnimPosStart =  mCamera->getPosition();

	    /* Set endpoint of animation so that the target desktop fills the viewport */
	    mAnimPosEnd = mDesktopNode[param]->getPosition();	    	    
	    distance = mDeskEntHeight / ( 2 * Math::Tan(mCamera->getFOVy() / 2));	    
	    mAnimPosEnd = mAnimPosEnd + mAnimOrientEnd * Ogre::Vector3(0, 0, distance);
	    
	    break;
	    
	case CA_ZoomOut:
	    /* Zoom out form desktop #param to mCameraPos/mCameraOrient */
	    mAnimFrames = 30;
	        
	    mAnimOrientStart = mDesktopNode[param]->getOrientation(); 
	    mAnimOrientEnd = mCameraOrient;

	    /* Set starting point of animation so that the desktop fills the viewport */
	    distance = mDeskEntHeight / ( 2 * Math::Tan(mCamera->getFOVy() / 2));	    
	    mAnimPosStart = mDesktopNode[param]->getPosition();
	    mAnimPosStart += mAnimOrientStart * Vector3(0, 0, distance);
	    
	    mAnimPosEnd = mCameraPos;
	    glueToGround(mAnimPosEnd);
	    
	    break;
	    
	case CA_None:;
	 
    }
    
    mTargetedDesktop = -1;
    mAnimCurrentFrame = 0;
    mCamera->setPosition(mAnimPosStart);
    mCamera->setOrientation(mAnimOrientStart);
    
    /* In-between orientations */
    mAnimOrientA = Quaternion::Slerp(0.2, mAnimOrientStart, mAnimOrientEnd, true);
    mAnimOrientB = Quaternion::Slerp(0.9, mAnimOrientStart, mAnimOrientEnd, true);
	    	    
}
 
void BaseOfficeScene::updateAnimation() {    
    if(mCurrentAnimation != CA_None) {
        /* Advance camera position and orientation */
	float fAnimPos = (float) mAnimCurrentFrame / mAnimFrames;        
	mCamera->setPosition(mAnimPosStart + (mAnimPosEnd - mAnimPosStart) * fAnimPos);	
	Quaternion newOrient = Quaternion::Squad(fAnimPos, mAnimOrientStart, mAnimOrientA, mAnimOrientB, mAnimOrientEnd, true);	
 	mCamera->setOrientation(newOrient);	    	
	
	/* Calculate mYaw and mPitch from current camera orientation */
	Plane p(mUp, 0);
 	Vector3 proj = p.projectVector(mCamera->getRealDirection());	
	Vector3 target = Vector3::UNIT_X;	
	Radian signed_angle (atan2(  mUp.dotProduct( proj.crossProduct(target) ), proj.dotProduct(target) )); 	
	
	mPitch = newOrient.getPitch();
	mYaw = - signed_angle - Degree(90);
	
	/* End animation if last frame is reached */
	if(mAnimCurrentFrame >= mAnimFrames) mCurrentAnimation = CA_None; else mAnimCurrentFrame++; 
    }    
}

void BaseOfficeScene::inputAction(int forward, int sidestep, float yaw, float pitch) {    
    if(mCurrentAnimation == CA_None) {

	/* Adjust yaw/pitch */
	mYaw -= Radian(yaw);
	mPitch -= Radian(pitch);

	/* Clip pitch to min/max range */
	Radian minPitch, maxPitch;
	getPitchLimits(minPitch, maxPitch);
	if(mPitch > maxPitch) mPitch = maxPitch;
	if(mPitch < minPitch) mPitch = minPitch;

	/* Set camera orientation */
	Quaternion yawQuat, pitchQuat;
	yawQuat.FromAngleAxis(mYaw, mUp);
	pitchQuat.FromAngleAxis(mPitch, Vector3::UNIT_X);		
	mCamera->setOrientation(yawQuat * pitchQuat);

	/* Determine "forward" vector parallel to the ground */
	Vector3 directionGround = yawQuat * (- Vector3::UNIT_X.crossProduct(mUp));
		
	/* Calculate movement delta vector */
	Vector3 delta(0,0,0);
	delta += forward * directionGround;
	delta += sidestep * directionGround.crossProduct(mUp);
	delta.normalise();
	/* movement speed = 5 units per action */
	delta *= 5;

	/* Move the viewpoint */
	if(delta.length() > 0) moveTo(mCamera->getPosition() + delta);
	
	/* Update currently targetted desktop */
	targetDesktop();
    }
}

void BaseOfficeScene::targetDesktop() {
    int newTarget = -1;
    
    if(mCurrentAnimation != CA_Switch) {
	
        /* Cast a ray in the camera's direciton, see if it hits a desktop */
	Ray ray(mCamera->getRealPosition(), mCamera->getDirection());
	for(int i = 0; i < mNumDesktops; i++) {
	    if(ray.intersects(mDesktopEnt[i]->getWorldBoundingBox()).first) {
		newTarget = i;
		break;
	    }	    
	}
    }
    
    if(newTarget != mTargetedDesktop) {
	mTargetedDesktop = newTarget;
	/* Update desktop meshes */
	for(int i = 0; i < mNumDesktops; i++) 
	    mFrameEnt[i]->setMaterialName(i == mTargetedDesktop ? "FrameHighlight" : "Frame");

    }
	
}

}